using System;
using System.Collections;
using Refly.CodeDom;
using Refly.CodeDom.Statements;
using Refly.CodeDom.Collections;
using Refly.Templates;
using Refly.CodeDom.Expressions;
using System.Text;
using System.Text.RegularExpressions;
using Doubler.Common;
using Reflector.Framework;
using Doubler.Data;
using Doubler.Helpers;
using Doubler.Helpers.Graph;

namespace Doubler.Templates.Recorder
{
    public class RecorderGenerator
    {

        #region Constants

        public const string RecordingCalled = "Called";
        public const string RecordingReturnValue = "ReturnValue";
        public const string RecordingExceptionToThrow = "ExceptionToThrow";
        public const string RecorderName = "Recorder";
        public const string RecordingsPropertyName = "Recordings";

        #endregion

        private Reflector.CodeModel.ITypeDeclaration _TypeObject = MetaHelper.Services.Search.FindType("System.Object");
        private RecorderActions.IRecorderAction _MethodActions;

        public RecorderActions.IRecorderAction MethodActions
        {
            get
            {
                if (_MethodActions == null)
                {
                    _MethodActions = new RecorderActions.RecordCalled();
                    _MethodActions.ActionDecorator = new RecorderActions.RecordParameters();
                    _MethodActions.ActionDecorator.ActionDecorator = new RecorderActions.ThrowException();
                    _MethodActions.ActionDecorator.ActionDecorator.ActionDecorator = new RecorderActions.ReturnValue();
                }
                return _MethodActions;
            }
            set
            {
                _MethodActions = value;
            }
        }

        public Reflector.CodeModel.ITypeDeclaration TypeObject
        {
            get
            {
                return _TypeObject;
            }
        }

        private System.CodeDom.MemberAttributes GetRecorderVisability(Reflector.CodeModel.ITypeReference subject, Reflector.CodeModel.IMethodDeclaration methodInformation)
        {
            System.CodeDom.MemberAttributes Visability;
            Visability = (System.CodeDom.MemberAttributes)System.Enum.Parse(typeof(System.CodeDom.MemberAttributes), methodInformation.Visibility.ToString());

            if (Reflector.Helper.GetMethod(TypeObject, methodInformation.Name) != null && Reflector.Helper.GetMethod(TypeObject, methodInformation.Name).Parameters.Equals(methodInformation.Parameters))
            {
                Visability = System.CodeDom.MemberAttributes.Override | Visability;
            }
            else if (methodInformation.DeclaringType == TypeObject)
            {
                Visability = System.CodeDom.MemberAttributes.New | Visability;
            }
            else if (Reflector.Helper.GetMethod(TypeObject, methodInformation.Name) != null)
            {
                Visability = System.CodeDom.MemberAttributes.Overloaded | Visability;
            }
            
            if (!subject.Resolve().Interface)
            {
                Visability = System.CodeDom.MemberAttributes.Override | Visability;
            }
            return Visability;
        }

        public void GenerateRecorderMethod(IClassDeclaration recorder, Reflector.CodeModel.ITypeReference subject, Reflector.CodeModel.IMethodDeclaration methodInformation, Hashtable Recorders)
        {
            MethodDeclaration Method = recorder.AddMethod(methodInformation.Name);
            Method.Signature.ReturnType = new StringTypeDeclaration(MetaHelper.GetFullTypeName(methodInformation.ReturnType.Type));

            foreach (Reflector.CodeModel.IParameterDeclaration ParameterInformation in methodInformation.Parameters)
            {
                Refly.CodeDom.StringTypeDeclaration ParameterType = new StringTypeDeclaration(MetaHelper.GetFullTypeName(ParameterInformation.ParameterType));
                ParameterDeclaration Param = Method.Signature.Parameters.Add(ParameterType, ParameterInformation.Name);
                
                if (ParameterInformation.ParameterType.ToString().Contains("&"))
                    Param.Direction = System.CodeDom.FieldDirection.Ref;
            }

            if (subject.Resolve().Interface)
            {
                Method.ImplementationTypes.Add(new StringTypeDeclaration(MetaHelper.GetFullTypeName(subject)));
            }

            System.CodeDom.MemberAttributes Visability = this.GetRecorderVisability(subject, methodInformation);

            Method.Attributes = Visability;

            String RecordingName = RecorderGenerator.GetRecordingName(methodInformation);
            if (!Recorders.Contains(RecordingName))
            {
                RecorderInfo Recorder = new RecorderInfo(RecordingName, methodInformation, Method.Signature.Parameters);
                Recorders.Add(RecordingName, Recorder);
            }

            Expression MasterRecorder = Expr.This.Prop(RecordingsPropertyName);
            NativePropertyReferenceExpression Recording = MasterRecorder.Prop(RecordingName);

            this.MethodActions.Act(recorder, Method, Recording, subject, methodInformation);
        }

        #region Helpers

        public static String GetRecordingName(Reflector.CodeModel.IMethodDeclaration method)
        {
            return GetRecordingName(method, method.Name);
        }

        public static String GetRecordingName(Reflector.CodeModel.IMethodDeclaration method, string name)
        {
            StringBuilder OverloadBuilder = new StringBuilder();
            foreach (Reflector.CodeModel.IParameterDeclaration ParameterInformation in method.Parameters)
            {
                String Param = String.Empty;
                if (ParameterInformation.ParameterType is Reflector.CodeModel.ITypeReference)
                {
                    Param = ((Reflector.CodeModel.ITypeReference)ParameterInformation.ParameterType).Name;
                }
                else
                {
                    Param = ParameterInformation.ParameterType.ToString();
                }
                Param = Regex.Replace(Param, @"\&", "");
                Param = Regex.Replace(Param, @"\@", "");
                Param = Regex.Replace(Param, @"\[\]", "Array");
                OverloadBuilder.Append(Param);
            }
            string Overloaded = OverloadBuilder.ToString();
            String RecordingName = string.Format("{0}{1}Recording", name, Overloaded);
            return RecordingName;
        }

        public static String GetRecorderName(string recordName)
        {
            String RecorderName = string.Format("{0}{1}", recordName, RecordingsPropertyName);
            return RecorderName;
        }

        public static string GetPassedRecordingName(ParameterDeclaration parameter)
        {
            return RecorderGenerator.GetPassedRecordingName(parameter.Type.Name, parameter.Name);
        }

        public static string GetPassedRecordingName(String typeName, String parameterName)
        {
            typeName = Regex.Replace(typeName, @"\&", "");
            typeName = Regex.Replace(typeName, @"\@", "");
            typeName = Regex.Replace(typeName, @"\[\]", "Array");
            string PassedRecordingName = string.Format("Passed{0}{1}", typeName, parameterName);
            return PassedRecordingName;
        }

        public static string GetRecorderClassTypeName(string subjectName)
        {
            subjectName = Regex.Replace(subjectName, @"\&", "");
            subjectName = Regex.Replace(subjectName, @"\@", "");
            subjectName = Regex.Replace(subjectName, @"\[\]", "Array");
            return string.Format("{0}{1}", RecorderName, subjectName);
        }

        public static string GetRecordingsClassTypeName(string subjectName)
        {
            subjectName = Regex.Replace(subjectName, @"\&", "");
            subjectName = Regex.Replace(subjectName, @"\@", "");
            subjectName = Regex.Replace(subjectName, @"\[\]", "Array");
            return string.Format("{0}{1}", subjectName, RecordingsPropertyName);
        }

        #endregion

    }
}
